/**
 * @file STL_Digest.cpp
 * @author Derek Liu (yjkhtddx@sina.com)
 * @brief 
 * @version 0.1
 * @date 2020-12-29
 * 
 * @copyright Copyright Sci-Tech Laboratories (c) 2020
 * 
 */

#include <stdio.h>
#include "STL_Digest.h"
#include "STL_Text.h"
#include "STL_MD5.h"
#include "STL_Base64.h"
#include "STL_Timestamp.h"
#include <map>

namespace STL
{
    STL_Digest::STL_Digest()
        : m_realm("Sci-Tech"), m_privateKey(STL_Text::genCharacterAndNumberPassword(16)), m_eTag("etag"), m_timeout(60) //60s timeout
    {
        m_userList.clear();
    }

    STL_Digest::~STL_Digest()
    {
    }

    void STL_Digest::addUserInfo(UserInfo info)
    {
        delUserInfo(info);
        m_userList.push_back(info);
    }
    void STL_Digest::delUserInfo(UserInfo info)
    {
        for (std::vector<UserInfo>::iterator it = m_userList.begin(); it != m_userList.end(); it++)
        {
            if (it->username == info.username)
            {
                m_userList.erase(it);
                return;
            }
        }
    }

    STL_Digest::UserInfo STL_Digest::getUserInfo(std::string username)
    {
        STL_Digest::UserInfo ret;
        ret.username.clear();
        ret.password.clear();
        for (int i = 0; i < m_userList.size(); i++)
        {
            if (username == m_userList.at(i).username)
                return m_userList.at(i);
        }
        return ret;
    }

    bool STL_Digest::checkNonce(std::string nonce)
    {
        std::string nonceContent = STL::base64Decode(nonce);
        size_t pos = nonceContent.find(" ");
        if (pos == std::string::npos)
            return false;

        std::string str_timestamp = nonceContent.substr(0, pos);
        long timestamp = atol(str_timestamp.c_str());
        //printf("%ld-%ld>%d => ",STL::getTimestmap(),timestamp,getTimeout());
        if (STL::getTimestamp() - timestamp > getTimeout())
        {
            printf("true\n");
            return false;
        }
        else
        {
            printf("false\n");
        }

        std::string timestamp_md5 = nonceContent.substr(pos + 1);
        printf("nonce[%s] ==> [%s]\n", nonce.c_str(), timestamp_md5.c_str());

        std::string check_timestamp_md5 = md5(str_timestamp + ":" + getETag() + ":" + getPrivateKey());
        printf("MD5(%s:%s:%s)=%s\n", str_timestamp.c_str(), getETag().c_str(), getPrivateKey().c_str(), check_timestamp_md5.c_str());

        if (check_timestamp_md5 == timestamp_md5)
        {
            return true;
        }
        else
        {
            return false;
        }
    };

    bool STL_Digest::checkAuthenticate(
        std::string username,
        std::string password,
        std::string method,
        std::string uri,
        std::string body,
        std::string realm,
        std::string nonce,
        std::string nc,
        std::string algorithm,
        std::string cnonce,
        std::string qop,
        std::string response)
    {
        std::string reqDigest = STL_Digest::genDigest(username, password, method, uri, body, realm, nonce, nc, algorithm, cnonce, qop);
        if (response == reqDigest)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    std::string STL_Digest::genDigestContent(std::map<std::string, std::string> map, std::string head)
    {
        std::string ret;
        for (std::map<std::string, std::string>::iterator it = map.begin(); it != map.end(); it++)
        {
            if (!ret.empty())
                ret = ret + ", ";
            ret = ret + it->first + "=\"" + it->second + "\"";
        }
        return head + ret;
    }

    std::map<std::string, std::string> STL_Digest::parseDigestContent(std::string content)
    {
        std::map<std::string, std::string> map;
        if (content.substr(0, 7) == "Digest ")
        {
            content = content.substr(7);
            std::vector<std::string> list = STL_Text::split(content, ", ");
            for (int i = 0; i < list.size(); i++)
            {
                size_t pos = list.at(i).find("=");
                if (pos != std::string::npos)
                {
                    std::string key = list.at(i).substr(0, pos);
                    std::string value = STL_Text::strRemove(list.at(i).substr(pos + 1), '"');
                    //printf("[%s]:%s\n",key.c_str(),value.c_str());
                    map[key] = value;
                }
            }
        }
        return map;
    }

    std::string STL_Digest::genDigest(
        std::string username,
        std::string password,
        std::string method,
        std::string uri,
        std::string body,
        std::string realm,
        std::string nonce,
        std::string nc,
        std::string algorithm,
        std::string cnonce,
        std::string qop)
    {
        std::string HA1, HD, HA2;
        if (algorithm == "MD5-sess")
        {
            HA1 = md5(md5(username + ":" + realm + ":" + password) + ":" + nonce + ":" + cnonce);
        }
        else
        {
            HA1 = md5(username + ":" + realm + ":" + password);
        }
        if (qop.empty())
        {
            HD = nonce;
        }
        else
        {
            HD = nonce + ":" + nc + ":" + cnonce + ":" + qop;
        }
        if (qop == "auth-int")
        {
            HA2 = md5(method + ":" + uri + ":" + md5(body));
        }
        else
        {
            HA2 = md5(method + ":" + uri);
        }
        return md5(HA1 + ":" + HD + ":" + HA2);
    }

    std::string STL_Digest::genNonce(std::string eTag, std::string privateKey)
    {
        std::string ret;
        long timstamp = STL::getTimestamp();
        ret = STL::base64Encode(std::to_string(timstamp) + " " + md5(std::to_string(timstamp) + ":" + eTag + ":" + privateKey));
        return ret;
    }

    std::string STL_Digest::genCnonce()
    {
        return STL_Text::StringToHexString(STL_Text::genCharacterAndNumberPassword(16));
    }

    std::string STL_Digest::genOpaque()
    {
        return STL_Text::StringToHexString(STL_Text::genCharacterAndNumberPassword(16));
    }

    std::string STL_Digest::getNonceCount(int count)
    {
        char nc[16];
        sprintf(nc, "%08d", count);
        return std::string(nc, 8);
    };

    std::string STL_Digest::getSupportQop(std::string qop)
    {
        std::vector<std::string> list = STL_Text::split(qop, ",");
        for (int i = 0; i < list.size(); i++)
        {
            if (list.at(i) == "auth")
                return "auth";
            if (list.at(i) == "auth-int")
                return "auth-int";
        }
        return "";
    }

    std::string STL_Digest::getMessageForWWWAuthenticate(bool stale) //WWW-Authenticate //服务器端
    {
        std::map<std::string, std::string> map;
        map["realm"] = getRealm();
        map["qop"] = "auth,auth-int";
        map["nonce"] = genNonce(getETag(), getPrivateKey());
        map["opaque"] = genOpaque();//TODO 这个字段可以为空
        map["algorithm"] = "MD5";//TODO 这个字段可以为空
        if (stale)
        {
            map["stale"] = "true";
        }
        else
        {
            map["stale"] = "false";
        }
        return genDigestContent(map, "Digest ");
    }
    std::string STL_Digest::getMessageForAuthenticationInfo(
        std::string username,
        std::string password,
        std::string uri,
        std::string body,
        std::string realm,
        std::string nc,
        std::string algorithm,
        std::string qop,
        std::string cnonce) //Authentication-Info //服务器端
    {
        std::map<std::string, std::string> map;
        map["nextnonce"] = genNonce(getETag(), getPrivateKey());
        map["qop"] = qop;
        map["cnonce"] = cnonce;
        map["nc"] = nc;
        map["rspauth"] = genDigest(username, password, "", uri, body, realm, map["nextnonce"], nc, algorithm, cnonce, qop);
        return STL_Digest::genDigestContent(map, "");
    }
    std::string STL_Digest::getMessageForAuthenticate(
        std::string username,
        std::string password,
        std::string method,
        std::string uri,
        std::string body,
        std::string realm,
        std::string nonce,
        int nonceCount,
        std::string algorithm,
        std::string cnonce,
        std::string qop,
        std::string opaque) //Authenticate 客户端使用
    {
        std::map<std::string, std::string> map;
        map["username"] = username;
        map["realm"] = realm;
        map["nonce"] = nonce;
        map["uri"] = uri;
        if(!opaque.empty()) map["opaque"] = opaque;
        if(!qop.empty()) map["qop"] = qop;
        if (nonceCount != 0) map["nc"] = STL_Digest::getNonceCount(nonceCount);
        if (!cnonce.empty()) map["cnonce"] = cnonce;
        map["response"] = genDigest(username, password, method, uri, body, realm, nonce, STL_Digest::getNonceCount(nonceCount), algorithm, cnonce, qop);
        return STL_Digest::genDigestContent(map, "Digest ");
    }
} // namespace STL